﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;
using System.Runtime.InteropServices;
using System.Windows;

namespace Interop
{
	public class MouseHook : Hook
	{
		#region statics
		private static MouseHook _MouseHook;
		public static MouseHook Hook(PresentationSource activeSource)
		{
			if (_MouseHook == null)
				_MouseHook = new MouseHook(activeSource);
			return _MouseHook;
		}
		#endregion statics

		#region ctors
		public MouseHook(PresentationSource activeSource) : base(HookType.WH_MOUSE_LL, activeSource) 
		{
			HookProc = MouseHookProc;
		}
		#endregion ctors

		#region props
		protected override bool AllEventsAreNull
		{
			get
			{
				return	_MouseMove == null &&
						_MouseDown == null &&
						_MouseUp == null &&
						_MouseWheel == null;
			}
		}
		#endregion props

		#region methods
		private IntPtr MouseHookProc(int nCode, IntPtr wParam, IntPtr lParam)
		{
			bool isMouseCode = true;
			bool handled = false;
			if (nCode >= 0)
			{
				MouseEventArgs mea = null;
				MouseButtonEventArgs mbea = null;
				MouseWheelEventArgs mwea = null;
				MouseHookStruct mhs = (MouseHookStruct)Marshal.PtrToStructure(lParam, typeof(MouseHookStruct));
				switch ((Win32MouseCodes)lParam)
				{
					case Win32MouseCodes.WM_MOUSEMOVE:
						if (_MouseMove != null)
						{
							mea = new MouseEventArgs(Mouse.PrimaryDevice, 0);
							mea.RoutedEvent = Mouse.MouseMoveEvent;
							_MouseMove.Invoke(this, mea);
							handled = mea.Handled;
						}
						break;

					case Win32MouseCodes.WM_LBUTTONDOWN:
						if (_MouseDown != null)
						{
							mbea = new MouseButtonEventArgs(Mouse.PrimaryDevice, 0, MouseButton.Left);
							mbea.RoutedEvent = Mouse.MouseDownEvent;
							_MouseDown.Invoke(this, mbea);
							handled = mbea.Handled;
						}
						break;
					case Win32MouseCodes.WM_MBUTTONDOWN:
						if (_MouseDown != null)
						{
							mbea = new MouseButtonEventArgs(Mouse.PrimaryDevice, 0, MouseButton.Middle);
							mbea.RoutedEvent = Mouse.MouseDownEvent;
							_MouseDown.Invoke(this, mbea);
							handled = mbea.Handled;
						}
						break;
					case Win32MouseCodes.WM_RBUTTONDOWN:
						if (_MouseDown != null)
						{
							mbea = new MouseButtonEventArgs(Mouse.PrimaryDevice, 0, MouseButton.Right);
							mbea.RoutedEvent = Mouse.MouseDownEvent;
							_MouseDown.Invoke(this, mbea);
							handled = mbea.Handled;
						}
						break;
					case Win32MouseCodes.WM_XBUTTONDOWN:
						if (_MouseDown != null)
						{
							mbea = new MouseButtonEventArgs(Mouse.PrimaryDevice, 0, MouseButton.XButton1);
							mbea.RoutedEvent = Mouse.MouseDownEvent;
							_MouseDown.Invoke(this, mbea);
							handled = mbea.Handled;
						}
						break;

					case Win32MouseCodes.WM_LBUTTONUP:
						if (_MouseUp != null)
						{
							mbea = new MouseButtonEventArgs(Mouse.PrimaryDevice, 0, MouseButton.Left);
							mbea.RoutedEvent = Mouse.MouseUpEvent;
							_MouseUp.Invoke(this, mbea);
							handled = mbea.Handled;
						}
						break;
					case Win32MouseCodes.WM_MBUTTONUP:
						if (_MouseUp != null)
						{
							mbea = new MouseButtonEventArgs(Mouse.PrimaryDevice, 0, MouseButton.Middle);
							mbea.RoutedEvent = Mouse.MouseUpEvent;
							_MouseUp.Invoke(this, mbea);
							handled = mbea.Handled;
						}
						break;
					case Win32MouseCodes.WM_RBUTTONUP:
						if (_MouseUp != null)
						{
							mbea = new MouseButtonEventArgs(Mouse.PrimaryDevice, 0, MouseButton.Right);
							mbea.RoutedEvent = Mouse.MouseUpEvent;
							_MouseUp.Invoke(this, mbea);
							handled = mbea.Handled;
						}
						break;
					case Win32MouseCodes.WM_XBUTTONUP:
						if (_MouseUp != null)
						{
							mbea = new MouseButtonEventArgs(Mouse.PrimaryDevice, 0, MouseButton.XButton1);
							mbea.RoutedEvent = Mouse.MouseUpEvent;
							_MouseUp.Invoke(this, mbea);
							handled = mbea.Handled;
						}
						break;

					case Win32MouseCodes.WM_MOUSEHWHEEL:
						int mouseDelta = (short)((mhs.MouseDelta >> 16) & 0xFFFF);
						mwea = new MouseWheelEventArgs(Mouse.PrimaryDevice, 0, mouseDelta);
						mwea.RoutedEvent = Mouse.MouseWheelEvent;
						_MouseWheel.Invoke(null, mwea);
						handled = mwea.Handled;
						break;
					default:
						isMouseCode = false;
						break;
				}
				if (UnhandledToHost && isMouseCode && !handled)
					handled = NativeMethods.PostMessage(_HostHwnd, (uint)wParam, (IntPtr)mhs.MouseDelta, (IntPtr)mhs.Time); //this is most definitely not right; must fix!
			}
			if (handled)
				return (IntPtr)(-1);
			return NativeMethods.CallNextHookEx(_hHook, nCode, wParam, lParam);
		}
		#endregion methods

		#region events
		private event MouseEventHandler _MouseMove;
		public event MouseEventHandler MouseMove
		{
			add
			{
				EnsureSubscriptionToEvents();
				_MouseMove += value;
			}
			remove
			{
				_MouseMove -= value;
				TryUnsubscribeFromEvents();
			}
		}
		private event MouseButtonEventHandler _MouseDown;
		public event MouseButtonEventHandler MouseDown
		{
			add
			{
				EnsureSubscriptionToEvents();
				_MouseDown += value;
			}
			remove
			{
				_MouseDown -= value;
				TryUnsubscribeFromEvents();
			}
		}
		private event MouseButtonEventHandler _MouseUp;
		public event MouseButtonEventHandler MouseUp
		{
			add
			{
				EnsureSubscriptionToEvents();
				_MouseUp += value;
			}
			remove
			{
				_MouseUp -= value;
				TryUnsubscribeFromEvents();
			}
		}
		private event MouseWheelEventHandler _MouseWheel;
		public event MouseWheelEventHandler MouseWheel
		{
			add
			{
				EnsureSubscriptionToEvents();
				_MouseWheel += value;
			}
			remove
			{
				_MouseWheel -= value;
				TryUnsubscribeFromEvents();
			}
		}
		#endregion events

		#region fields
		#endregion fields
	}
}
	//#define WM_MOUSEMOVE                    0x0200
	//#define WM_LBUTTONDOWN                  0x0201
	//#define WM_LBUTTONUP                    0x0202
	//#define WM_LBUTTONDBLCLK                0x0203
	//#define WM_RBUTTONDOWN                  0x0204
	//#define WM_RBUTTONUP                    0x0205
	//#define WM_RBUTTONDBLCLK                0x0206
	//#define WM_MBUTTONDOWN                  0x0207
	//#define WM_MBUTTONUP                    0x0208
	//#define WM_MBUTTONDBLCLK                0x0209
	//#define WM_XBUTTONDOWN                  0x020B
	//#define WM_XBUTTONUP                    0x020C
	//#define WM_XBUTTONDBLCLK                0x020D
	//#define WM_MOUSEHWHEEL                  0x020E
